package com.ssl.product.config;

import com.mongodb.MongoClientSettings;
import com.mongodb.MongoDriverInformation;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.client.internal.MongoClientImpl;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.convert.*;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableConfigurationProperties(MongoConfig.MongoClientOptionProperties.class)
public class MongoConfig {
    //从我们的 application.yml 里读取并封装这些配置
    @Getter
    @Setter
    @Accessors(chain = true)
    @ConfigurationProperties(prefix = "data.mongodb")
    public class MongoClientOptionProperties {
        private String database;
        private String username;
        private String password;
        private String authenticationDatabase;
        private String address;
        private String clientName;

        private int connectionTimeoutMs; // TCP（socket）连接超时时间，毫秒
        private int maxConnectionIdleTimeMs; // TCP（socket）连接闲置时间，毫秒
        private int readTimeoutMs; // TCP（socket）读取超时时间，毫秒
        private int maxWaitTimeMs; // 当连接池无可用连接时客户端阻塞等待的最大时长，毫秒
        private int heartbeatFrequencyMs; // 心跳检测发送频率，毫秒
        private int minHeartbeatFrequencyMs; // 最小的心跳检测发送频率，毫秒
        private int connectionsPerHost; // 线程池允许的最大连接数
        private int minConnectionsPerHost;
    }
    @Bean
    public MongoTemplate getMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
        //为了创建我们的 MongoTemplate，我们需要 mongoDbFactory 和 mongoConverter；
        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, mongoConverter);
        // mongodb 也是支持读写分离的，人家也是cluster集群，主从，高扩展的，所以我们如果有从库，我们需要从 从库进行数据读取的话，我们
        //需要如下的一行代码进行设置：
        //？ mongoTemplate如何知道哪个是主，哪个是从呢？mongoTemplate如何知道哪个是主 从 mongoDbFactory，
        // mongoDbFactory 从 mongoClientSettings来的
        // mongoClientSettings里边有  .applyToClusterSettings(c -> c.hosts(serverList))
        mongoTemplate.setReadPreference(ReadPreference.secondaryPreferred());
        return mongoTemplate;
    }

    // User 类中有 name，age，我们save一条到mongodb
    // mogodb 会存储 name，age，_id,_class
    //我们的 converter可以把 _class 移除
    @Bean
    public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory factory, MongoMappingContext context) {
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
        MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, context);
        mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null)); //移除_class
        return mappingMongoConverter;
    }

    @Bean
    public MongoTypeMapper defaultTypeMapper() {
        return new DefaultMongoTypeMapper(null);
    }

    @Bean
    public MongoDatabaseFactory mongoDatabaseFactory(MongoClientOptionProperties properties) {
        MongoClientSettings mongoClientSettings = mongoClientSettings(properties);
        MongoDriverInformation information = MongoDriverInformation.builder().build();
        MongoClientImpl mongoClient = new MongoClientImpl(mongoClientSettings, information);
        return new SimpleMongoClientDatabaseFactory(mongoClient, properties.getDatabase());
    }

    //mongoDBFacotry
    @Bean
    public MongoClientSettings mongoClientSettings(MongoClientOptionProperties properties) {
//        MongoCredential credential = null;
//        if(StringUtils.isNotEmpty(properties.getUsername()) && StringUtils.isNotEmpty(properties.getPassword())) {
//            //mongodb的认证库
//            credential = MongoCredential.createCredential(
//                    properties.getUsername(),
//                    properties.getAuthenticationDatabase() == null? properties.getDatabase() : properties.getAuthenticationDatabase(),
//                    properties.getPassword().toCharArray());
//        }
        List<ServerAddress> serverList = new ArrayList<>();
        serverList.add(new ServerAddress("localhost", 27017)); // 其实真正的代码需要进行address的，分割以及 ：分割
        return MongoClientSettings.builder()
                .applicationName(properties.getClientName())
//                .credential(credential)
                .applyToClusterSettings(c -> c.hosts(serverList))
                .applyToSocketSettings(s -> s
                        .readTimeout(properties.getReadTimeoutMs(), TimeUnit.MILLISECONDS)
                        .connectTimeout(properties.connectionTimeoutMs, TimeUnit.MILLISECONDS))
                .applyToConnectionPoolSettings(p -> p
                        .maxConnectionIdleTime(properties.maxConnectionIdleTimeMs, TimeUnit.MILLISECONDS)
                        .maxWaitTime(properties.getMaxWaitTimeMs(), TimeUnit.MILLISECONDS)
                        .maxSize(properties.getConnectionsPerHost())
                        .minSize(properties.getMinConnectionsPerHost()))
                .applyToServerSettings(ser -> ser.heartbeatFrequency(properties
                                .getHeartbeatFrequencyMs(), TimeUnit.MILLISECONDS)
                        .minHeartbeatFrequency(properties.getMinHeartbeatFrequencyMs(), TimeUnit.MILLISECONDS))
                .build();
    }
}
